﻿/* NOTE:
 * 
 * 使用FileLogger需要有创建文件和使用文件的权限
 * 
 * VISTA 和 Windows 7 都使用了UAC来控制程序访问，对于一些需要使用管理员身份运行的程序就得右键以管理员身份运行。
 * 
 * C# 编程中可以使程序自动使用管理员身份运行，也就是我们常常看到一些程序的图标上会冒出个盾牌。

 * 方法:

 * 在源码的Properties目录中找到 app.manifest,

 * 将其中level="asInvoker" 改成 level="requireAdministrator"就可以了，

 * 如果没有app.manifest文件可以打开项目属性，找到安全性项，勾上启用 ClickOnce 安全设置，这时会生成app.manifest文件，

 * 然后再把勾去掉，不要选上。

 * 这样你重新编译，你的程序图标上就会出现个小盾牌了，就自动以管理员身份运行你的程序，无需右键打开。
 */

using System;
using System.IO;
using System.Text;
using System.Security.AccessControl;
using Alex.Library.Log.Exceptions;
using System.Text.RegularExpressions;

namespace Alex.Library.Log
{
    /// <summary>
    /// 文件日志
    /// </summary>
    public class FileLogger : LoggerBase
    {
        #region Ctors

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logDir">存放日志的目录</param>
        /// <param name="ext">日志文件类型</param>
        public FileLogger(string logDir, string ext)
        {
            this.LogDir = logDir;
            this.FileExtension = ext;
            DirectoryInfo dir = null;
            if (!Directory.Exists(this.LogDir))
            {
                dir = Directory.CreateDirectory(this.LogDir);
            }
            else
            {
                dir = new DirectoryInfo(this.LogDir);
            }
        }

        /// <summary>
        /// 构造函数，日志文件扩展名默认为.log
        /// </summary>
        /// <param name="logDir">存放日志的目录</param>
        public FileLogger(string logDir) : this(logDir, ".log") { }

        /// <summary>
        /// 构造函数，日志存放目录由AppDomain.CurrentDomain.BaseDirectory确定,文件扩展名默认为.log
        /// </summary>
        public FileLogger() : this(AppDomain.CurrentDomain.BaseDirectory) { }

        #endregion

        #region Properties

        /// <summary>
        /// 日志存放目录
        /// </summary>
        public string LogDir { get; set; }

        /// <summary>
        /// 日志文件名(不包含路径)
        /// </summary>
        public string FileName { get; private set; }

        /// <summary>
        /// 日志文件全名
        /// </summary>
        public string FullName { get; private set; }

        /// <summary>
        /// 文件扩展名，默认为.log
        /// </summary>
        public string FileExtension { get; set; }

        /// <summary>
        /// 如果有同名文件是否覆盖，如果为false则以追加方式输出
        /// </summary>
        public bool OverrideIfExist { get; set; }

        private CreateFileBehavior _Behavior = CreateFileBehavior.ByDay;
        /// <summary>
        /// 创建日志文件行为
        /// </summary>
        public CreateFileBehavior Behavior
        {
            get { return _Behavior; }
            set
            {
                _Behavior = value;
            }
        }

        private double _MaxAchieveSize;
        /// <summary>
        /// 日志文件大小的上限(单位：KB)，超过此数值时将重新创建一个文件（新文件名为FileName[序号].FileExt），0表示不设上限
        /// 当CreateFileBehavior = ByFileSize时，不能为0
        /// </summary>
        public double MaxAchieveSize
        {
            get
            {
                return _MaxAchieveSize / 1024;
            }
            set
            {
                _MaxAchieveSize = value * 1024;
            }
        }

        #endregion

        #region Functions

        /// <summary>
        /// 基类方法
        /// </summary>
        /// <param name="level"></param>
        /// <param name="message"></param>
        protected override void DoFlush(LogLevel level, string message)
        {
            ValidateConfig();

            FileStream fs = null;
            try
            {
                SetFileIfAppendOrNew();
                fs = new FileStream(this.FullName, FileMode.Append, FileAccess.Write, FileShare.Write);
                string content = this.Template.Format(level, message);
                byte[] buffer = Encoding.UTF8.GetBytes(content);
                fs.Write(buffer, 0, buffer.Length);
            }
            catch (Exception ex)
            {
                //throw ex;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                    fs.Dispose();
                }
            }
        }

        /// <summary>
        /// 验证配置
        /// </summary>
        public virtual void ValidateConfig()
        {
            if (this._Behavior == CreateFileBehavior.ByFileSize && _MaxAchieveSize == 0)
            {
                throw new InvalidLogConfigException("当创建日志文件行为是ByFileSize时，日志文件大小的上限(MaxAchieveSize)不能为0.");
            }
        }

        /// <summary>
        /// 判断是需要创建新的文件还是追加内容，如果是创建新的则FullName属性将会改变为新文件名
        /// </summary>
        protected virtual void SetFileIfAppendOrNew()
        {
            if (String.IsNullOrEmpty(this.FullName))
            {
                string prefix = GetFileNamePrefix();
                this.FullName = this.LogDir + @"\" + prefix + this.FileExtension;
            }
            FileInfo file = new FileInfo(this.FullName);
            this.FileName = file.Name;
            bool continueUse = false;
            switch (_Behavior)
            {
                case CreateFileBehavior.ByDay:
                    continueUse = file.CreationTime.Date.Equals(DateTime.Now.Date);
                    break;
                case CreateFileBehavior.ByWeek:
                    int fileOfWeek = file.CreationTime.WeekOfYear();
                    int nowOfWeek = DateTime.Now.WeekOfYear();
                    continueUse = fileOfWeek.Equals(nowOfWeek);
                    break;
                case CreateFileBehavior.ByMonth:
                    continueUse = file.CreationTime.Month.Equals(DateTime.Now.Month);
                    break;
                case CreateFileBehavior.ByYear:
                    continueUse = file.CreationTime.Year.Equals(DateTime.Now.Year);
                    break;
            }
            if (_MaxAchieveSize > 0)
            {
                continueUse = continueUse && file.Length < _MaxAchieveSize;
            }

            if (!continueUse)
            {
                ResetFullName();
            }

            if (this.OverrideIfExist)
            {
                try
                {
                    file.Delete();
                }
                catch
                { }
            }
        }

        /// <summary>
        /// 重置FullName
        /// </summary>
        protected void ResetFullName()
        {
            string prefix = GetFileNamePrefix();
            int index = GetFileIndex(prefix);
            this.FileName = (index >= 0) ? prefix + "[" + (index++).ToString() + "]" + this.FileExtension
                : prefix + this.FileExtension;
            this.FullName = this.LogDir + @"\" + this.FileName;
        }

        /// <summary>
        /// 获取文件名前缀
        /// </summary>
        /// <returns></returns>
        protected string GetFileNamePrefix()
        {
            string name;
            switch (_Behavior)
            {
                case CreateFileBehavior.ByDay:
                    name = String.Format("{0:yyyyMMdd}", DateTime.Now);
                    break;
                case CreateFileBehavior.ByWeek:
                    int nowOfWeek = DateTime.Now.WeekOfYear();
                    name = DateTime.Now.Year.ToString() + nowOfWeek.ToString().PadLeft(2, '0');
                    break;
                case CreateFileBehavior.ByMonth:
                    name = String.Format("{0:yyyyMM}", DateTime.Now);
                    break;
                case CreateFileBehavior.ByYear:
                    name = DateTime.Now.Year.ToString();
                    break;
                default:
                    name = "app";
                    break;
            }
            return name;
        }

        /// <summary>
        /// 获取日志目录中保存的以prefix为前缀的文件的索引,
        /// 如果只有一个不带索引的文件名则返回0，表示此文件是默认的第一个文件
        /// (20120208.log为2012.2.8的默认的第一个文件，当天的其他文件为20120208[1].log、20120208[2].log...),
        /// 如果没有查询到任何匹配的文件则返回-1，表示应该新建一个以prefix为名称的文件
        /// </summary>
        /// <param name="prefix">文件名前缀</param>
        /// <returns></returns>
        protected int GetFileIndex(string prefix)
        {
            DirectoryInfo directory = new DirectoryInfo(this.LogDir);
            FileInfo[] files = directory.GetFiles(prefix + "*" + this.FileExtension, 
                SearchOption.TopDirectoryOnly);

            if (files != null && files.Length > 0)
            {
                int lastIndex = 0;

                string pattern = prefix + @"\[(?<index>\d+)\]" + this.FileExtension;

                foreach (FileInfo file in files)
                {
                    string value = Regex.Match(file.Name, pattern).Groups["index"].Value;
                    int index = !String.IsNullOrEmpty(value) ? Convert.ToInt32(value) : 0;
                    if (lastIndex < index)
                    {
                        lastIndex = index;
                    }
                }
                //如果只有一个不带索引的文件名则返回0，表示此文件是默认的第一个文件
                return lastIndex;
            }

            //如果没有查询到任何匹配的文件则返回-1，表示此文件是新的并且是第一个日志文件
            return -1;
        }

        /// <summary>
        /// 设置目录权限
        /// </summary>
        /// <param name="account">具有管理员权限的账户：DomainName\UserName</param>
        /// <param name="rights">目录权限</param>
        public void SetDirectoryAccessControl(string account, FileSystemRights rights)
        {
            DirectoryInfo dir = new DirectoryInfo(this.LogDir);
            bool modified;
            DirectorySecurity dSecurity = dir.GetAccessControl();

            FileSystemAccessRule AccessRule2 = new FileSystemAccessRule(account, rights,
                InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                PropagationFlags.None, AccessControlType.Allow);

            dSecurity.ModifyAccessRule(AccessControlModification.Add, AccessRule2, out modified);

            dir.SetAccessControl(dSecurity);
        }

        #endregion
    }
}
